package com.signway.swmediaplayer;

import static com.signway.swmediaplayer.SWMediaManager.VIDEO;
import static com.signway.swmediaplayer.utils.Utils.setNextExtractor;

import android.media.MediaCodec;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.os.Build;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Log;
import android.view.SurfaceHolder;

import com.signway.swmediaplayer.utils.SWLog;
import com.signway.swmediaplayer.utils.SystemProperties;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

// 转换时间，转换后大小，
// h264 转换 h264
// h264 转换 h265
// h265 转换 h265
// h265 转换 h264

public class SWVideoManager {
    private SWLog swLog = new SWLog(SWVideoManager.class.getName(), true, true, false);

    private enum VideoCommStatus {
        VIDEO_COMM_STATUS_IDLE,
        VIDEO_COMM_STATUS_END,
        VIDEO_COMM_STATUS_ERROR,
        VIDEO_COMM_STATUS_INITIALIZED,
        VIDEO_COMM_STATUS_PREPARED,
        VIDEO_COMM_STATUS_NEXT_PREPARED,
        VIDEO_COMM_STATUS_PREPARING,
        VIDEO_COMM_STATUS_NEXT_PREPARING,
        VIDEO_COMM_STATUS_STARTED,
        VIDEO_COMM_STATUS_NEXT_STARTED,
        VIDEO_COMM_STATUS_CODEC_END,
        VIDEO_COMM_STATUS_RENDER_END,
        VIDEO_COMM_STATUS_PAUSED,
        VIDEO_COMM_STATUS_STOPPED,
        VIDEO_COMM_STATUS_PLAYBACK_COMPLETED,
    };
    private volatile VideoCommStatus mVideoCommStatus;

    private String mFilePath = null;
    private long mRenderStartTimeNs = 0;
    private long mPauseTimeNs = 0;
    private long mCodecStartTimeUs = 0;
    private long mCodecLastTimeUs = 0;
    private boolean mVideoDecoderFlush = false;/* 解码器执行flash标记,用于首帧立即显示 */
    private SWMediaManager mMediaManager = null;

    public SWVideoManager(SWMediaManager mediaManager) {
        swLog.logd("SWVideoManager()");
        mMediaManager = mediaManager;
        mVideoCommStatus = VideoCommStatus.VIDEO_COMM_STATUS_IDLE;
        videoCommThread = new Thread(videoCommRunnable);
        videoCommThread.start();
    }

    private SWMediaManager.OnMediaRenderListener mOnMediaRenderListener = null;
    public synchronized void setOnMediaRenderListener(SWMediaManager.OnMediaRenderListener listener) {
        mOnMediaRenderListener = listener;
    }

    public synchronized void switchDataSource() {
        if (mVideoCommStatus != VideoCommStatus.VIDEO_COMM_STATUS_STARTED) {
            swLog.logd("don't allow switchDataSource");
            return;
        }
        mVideoCommStatus = VideoCommStatus.VIDEO_COMM_STATUS_NEXT_STARTED;
    }

    public synchronized void setStartWhenNs(long startWhenNs) {
        mRenderStartTimeNs = startWhenNs;
    }

    public synchronized long getStartWhenNs() {
        return mRenderStartTimeNs;
    }

    public synchronized void setNextDataSource(String path) throws IOException, InterruptedException {
        if (TextUtils.isEmpty(path)) {
            throw new IOException("data source is empty");
        }
        if (mVideoCommStatus != VideoCommStatus.VIDEO_COMM_STATUS_CODEC_END) {
            throw new IOException("the current state is not codec end " + mVideoCommStatus);
        }
        File file = new File(path);
        if (!(file.exists() && file.isFile() && file.canRead())) {
            throw new IOException("data source is error");
        }
        swLog.logd("setNextDataSource " + path);
        mFilePath = path;

        mVideoCommStatus = VideoCommStatus.VIDEO_COMM_STATUS_NEXT_PREPARING;
        while (mVideoCommStatus == VideoCommStatus.VIDEO_COMM_STATUS_NEXT_PREPARING) {
            this.wait();
        }
        if (mVideoCommStatus == VideoCommStatus.VIDEO_COMM_STATUS_ERROR) {
            throw new IOException("setNextDataSource error");
        }
        swLog.logd("setNextDataSource() finish.");
    }

    public synchronized void setCodecStartTimeUs(long codecStartTimeUs) {
        mCodecStartTimeUs = codecStartTimeUs;
        swLog.logd("setCodecStartTimeUs " + codecStartTimeUs);
    }

    public synchronized void setDataSource(String path) throws IOException {
        if (TextUtils.isEmpty(path)) {
            throw new IOException("data source is empty");
        }
        if (mVideoCommStatus != VideoCommStatus.VIDEO_COMM_STATUS_IDLE) {
            throw new IOException("the current state is not idle");
        }
        File file = new File(path);
        if (!(file.exists() && file.isFile() && file.canRead())) {
            throw new IOException("data source is error");
        }
        swLog.logd("setDataSource " + path);
        mFilePath = path;
        mVideoCommStatus = VideoCommStatus.VIDEO_COMM_STATUS_INITIALIZED;
    }

    public synchronized void setCustomFormat(MediaFormat format) {
        mCustomFormat = format;
        swLog.logi("set custom format " + mCustomFormat);
    }

    public synchronized void setDisplay(SurfaceHolder sh) {
        if (mVideoCommStatus != VideoCommStatus.VIDEO_COMM_STATUS_IDLE &&
                mVideoCommStatus != VideoCommStatus.VIDEO_COMM_STATUS_INITIALIZED) {
            swLog.loge("Not allowed to set.");
            return;
        }
        mSurfaceHolder = sh;
    }

    public synchronized void prepare() throws IOException, IllegalStateException, InterruptedException {
        if (mVideoCommStatus != VideoCommStatus.VIDEO_COMM_STATUS_INITIALIZED) {
            swLog.loge("the current state is not initialized");
            return;
        }
        swLog.logd("prepare()");
        mVideoCommStatus = VideoCommStatus.VIDEO_COMM_STATUS_PREPARING;
        while (mVideoCommStatus == VideoCommStatus.VIDEO_COMM_STATUS_PREPARING) {
            this.wait();
        }
        if (mVideoCommStatus == VideoCommStatus.VIDEO_COMM_STATUS_ERROR) {
            throw new IOException("prepare error");
        }
        swLog.logd("prepare() finish.");
    }

    public synchronized void start() {
        switch (mVideoCommStatus) {
            case VIDEO_COMM_STATUS_PREPARED:
            case VIDEO_COMM_STATUS_NEXT_PREPARED:
            case VIDEO_COMM_STATUS_CODEC_END:
                mVideoCommStatus = VideoCommStatus.VIDEO_COMM_STATUS_STARTED;
                break;
            case VIDEO_COMM_STATUS_PAUSED:
                mPauseTimeNs = System.nanoTime() - mPauseTimeNs;
                mRenderStartTimeNs += mPauseTimeNs;
                mVideoCommStatus = VideoCommStatus.VIDEO_COMM_STATUS_STARTED;
                break;
            default:
                swLog.loge("the current state is not prepared");
                break;
        }
    }

    public synchronized void pause() {
        if (mVideoCommStatus != VideoCommStatus.VIDEO_COMM_STATUS_STARTED &&
            mVideoCommStatus != VideoCommStatus.VIDEO_COMM_STATUS_NEXT_PREPARED) {
            swLog.logd("pause() error " + mVideoCommStatus);
            return;
        }
        swLog.logd("pause()");
        mVideoCommStatus = VideoCommStatus.VIDEO_COMM_STATUS_PAUSED;
        mPauseTimeNs = System.nanoTime();
    }

    public void release() throws InterruptedException {
        long start = SystemClock.elapsedRealtime();
        swLog.logd("video release()");
        synchronized (SWVideoManager.this) {
            if (mVideoCommStatus == VideoCommStatus.VIDEO_COMM_STATUS_END) {
                return;
            }
            mOnMediaRenderListener = null;
            mVideoCommStatus = VideoCommStatus.VIDEO_COMM_STATUS_END;
            videoCommThread.interrupt();
        }
        semaphore.acquireUninterruptibly(1);
        semaphore.release();
        swLog.logd("video release() finish. " + (SystemClock.elapsedRealtime() - start) + "(ms)");
    }

    public synchronized void pushEos() {
        if (mVideoCommStatus != VideoCommStatus.VIDEO_COMM_STATUS_CODEC_END) {
            return;
        }
        pushEosInputBuffer();
    }

    public synchronized void reset() {
        mVideoCommStatus = VideoCommStatus.VIDEO_COMM_STATUS_IDLE;
        mCustomFormat = null;
    }

    private Semaphore semaphore = new Semaphore(1);
    private Runnable videoCommRunnable = () -> {
        long sleepTime = 20;
        swLog.logi("videoCommRunnable start.");
        try {
            semaphore.acquire();
            swLog.logi("mainThreadProgress start.");
            while (!Thread.interrupted()) {
                mainThreadProgress();
                synchronized (mVideoCommStatus) {
                    if (mVideoCommStatus == VideoCommStatus.VIDEO_COMM_STATUS_END ||
                            mVideoCommStatus == VideoCommStatus.VIDEO_COMM_STATUS_STOPPED) {
                        break;
                    }
                    if (mVideoCommStatus == VideoCommStatus.VIDEO_COMM_STATUS_STARTED ||
                            mVideoCommStatus == VideoCommStatus.VIDEO_COMM_STATUS_CODEC_END ||
                            mVideoCommStatus == VideoCommStatus.VIDEO_COMM_STATUS_NEXT_PREPARED) {
                        sleepTime = 5;
                    } else {
                        sleepTime = 20;
                    }
                }
                TimeUnit.MILLISECONDS.sleep(sleepTime);
            }
            swLog.logi("mainThreadProgress end.");
        } catch (InterruptedException e) {
            swLog.loge("videoCommRunnable interrupt " + e);
        } catch (Exception e) {
            e.printStackTrace();
            swLog.loge("videoCommRunnable interrupt " + e);
        } finally {
            videoCodecRelease(false);
            semaphore.release();
        }
        swLog.logi("videoCommRunnable end.");
    };

    private SurfaceHolder mSurfaceHolder = null;
    private long mLastRenderTimestampNs = 0;/* 当前解码最后一帧显示时间 */

    private boolean mFirstFrameQuery = false;/* 首帧查询 */
    private ArrayList<Long> mFirstFramePtsList = new ArrayList<>();/* 首帧查询PTS列表 */

    private Thread videoCommThread = null;
    private MediaFormat mVideoFormat = null;
    private MediaFormat mCustomFormat = null;
    private MediaExtractor mVideoExtractor = null;
    private MediaCodec mVideoDecoder = null;
    MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
    ByteBuffer[] inputBuffers = null;
    ByteBuffer[] outputBuffers = null;

    private void videoCodecRelease(boolean wait) {
        swLog.logi("release start.");

        swLog.logi("videoCodecRelease delayMs " + ((mLastRenderTimestampNs - System.nanoTime()) / 1_000_000));
        while (wait) {
            if (System.nanoTime() - mLastRenderTimestampNs >= 0) {
                break;
            }
        }
        swLog.logi("wait last render finish.");

        try {
            if (mVideoDecoder != null) {
                swLog.logi("decoder stop.");
                mVideoDecoder.release();
                swLog.logi("decoder release.");
            }
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } finally {
            mVideoDecoder = null;
        }

        try {
            if (mVideoExtractor != null) {
                mVideoExtractor.release();
            }
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } finally {
            mVideoFormat = null;
            mVideoExtractor = null;
        }

        mVideoDecoderFlush = true;

        swLog.logi("release end.");
    }

    private boolean videoCodecInit() throws IOException {
        videoCodecRelease(true);
        mVideoExtractor = new MediaExtractor();
        mVideoExtractor.setDataSource(mFilePath);
        swLog.logd("setDataSource " + mFilePath);
        swLog.logd(mFilePath + ", getTrackCount: " + mVideoExtractor.getTrackCount());
        for (int i = 0; i < mVideoExtractor.getTrackCount(); i++) {
            mVideoFormat = mVideoExtractor.getTrackFormat(i);
            String mime = mVideoFormat.getString(MediaFormat.KEY_MIME);
            if (mime.startsWith(VIDEO)) {
                swLog.logi("format : " + mVideoFormat + ", mime: " + mime);
                mVideoExtractor.selectTrack(i);
                mVideoDecoder = MediaCodec.createDecoderByType(mime);
                mVideoFormat.setLong(MediaFormat.KEY_DURATION, 0x7ffffffffffffffL);

                int height = mVideoFormat.getInteger(MediaFormat.KEY_HEIGHT);
                int width = mVideoFormat.getInteger(MediaFormat.KEY_WIDTH);
                mVideoFormat.setLong(MediaFormat.KEY_MAX_INPUT_SIZE, (long) width * height);

                if (mCustomFormat != null) {
                    if (mCustomFormat.containsKey(MediaFormat.KEY_MAX_INPUT_SIZE)) {
                        mVideoFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE,
                                mCustomFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE));
                    }
                    if (mCustomFormat.containsKey(MediaFormat.KEY_ROTATION)) {
                        mVideoFormat.setInteger(MediaFormat.KEY_ROTATION,
                                mCustomFormat.getInteger(MediaFormat.KEY_ROTATION));
                    }
                    if (mCustomFormat.containsKey(MediaFormat.KEY_FRAME_RATE)) {
                        mVideoFormat.setInteger(MediaFormat.KEY_FRAME_RATE,
                                mCustomFormat.getInteger(MediaFormat.KEY_FRAME_RATE));
                    }
                }

                SystemProperties.setProp("vendor.mstar.mmvideo.keep-lastframe", "true");
                mVideoFormat.setInteger("vendor.vdecomx.disable-keep-lastframe", 0);
                mVideoDecoder.configure(mVideoFormat, mSurfaceHolder.getSurface(), null, 0 /* Decoder */);
                swLog.logd("mVideoDecoder configure" + mVideoFormat + ", " + mVideoDecoder.getName());

                mVideoDecoder.start();
                inputBuffers = mVideoDecoder.getInputBuffers();
                outputBuffers = mVideoDecoder.getOutputBuffers();
                swLog.logd("video inputBuffers length is " + inputBuffers.length);
                swLog.logd("video outputBuffers length is " + outputBuffers.length);
                return true;
            }
        }
        return false;
    }

    /**
     * 主事件循环
     */
    private synchronized void mainThreadProgress() throws InterruptedException {
        long startMs = SystemClock.elapsedRealtime();
        swLog.logv("mainThreadProgress mVideoCommStatus: " + mVideoCommStatus);
        switch (mVideoCommStatus) {
            case VIDEO_COMM_STATUS_IDLE:
                swLog.logi("VIDEO_COMM_STATUS_IDLE");
                break;
            case VIDEO_COMM_STATUS_END:
                swLog.logi("VIDEO_COMM_STATUS_END");
                break;
            case VIDEO_COMM_STATUS_ERROR:
                swLog.logi("VIDEO_COMM_STATUS_ERROR");
                break;
            case VIDEO_COMM_STATUS_INITIALIZED:
                swLog.logi("VIDEO_COMM_STATUS_INITIALIZED");
                break;
            case VIDEO_COMM_STATUS_PREPARED:
                swLog.logi("VIDEO_COMM_STATUS_PREPARED");
                break;
            case VIDEO_COMM_STATUS_PREPARING:
                swLog.logi("VIDEO_COMM_STATUS_PREPARING");
                try {
                    if (videoCodecInit()) {
                        swLog.logi("videoCodecInit Finish");
                        mVideoCommStatus = VideoCommStatus.VIDEO_COMM_STATUS_PREPARED;
                        mFirstFrameQuery = true;
                        mFirstFramePtsList.clear();
                    } else {
                        mVideoCommStatus = VideoCommStatus.VIDEO_COMM_STATUS_ERROR;
                    }
                } catch (IOException e) {
                    mVideoCommStatus = VideoCommStatus.VIDEO_COMM_STATUS_ERROR;
                }
                this.notify();
                break;
            case VIDEO_COMM_STATUS_NEXT_PREPARING:
                swLog.logi("VIDEO_COMM_STATUS_NEXT_PREPARING");
                try {
                    mVideoExtractor = setNextExtractor(mVideoExtractor, mFilePath, VIDEO);
                    mVideoCommStatus = VideoCommStatus.VIDEO_COMM_STATUS_NEXT_PREPARED;
//                    mFirstFrameQuery = true;
//                    mFirstFramePtsList.clear();
                } catch (IOException e) {
                    mVideoCommStatus = VideoCommStatus.VIDEO_COMM_STATUS_ERROR;
                }
                this.notify();
                break;
            case VIDEO_COMM_STATUS_STARTED:
            case VIDEO_COMM_STATUS_NEXT_STARTED:
                swLog.logv("VIDEO_COMM_STATUS_STARTED");
                pushInputBuffer();
            case VIDEO_COMM_STATUS_NEXT_PREPARED:
            case VIDEO_COMM_STATUS_CODEC_END:
            case VIDEO_COMM_STATUS_PAUSED:
                getOutputBuffer();
                break;
            case VIDEO_COMM_STATUS_STOPPED:
                swLog.logi("VIDEO_COMM_STATUS_STOPPED");
                break;
            case VIDEO_COMM_STATUS_PLAYBACK_COMPLETED:
                swLog.logi("VIDEO_COMM_STATUS_PLAYBACK_COMPLETED");
                break;
        }
        swLog.logv("mainThreadProgress time " + (SystemClock.elapsedRealtime() - startMs));
    }

    /* SWMediaManager调用 */
    private void pushEosInputBuffer() {
        swLog.logi("pushEosInputBuffer start");
        while (true) {
            synchronized (SWVideoManager.this) {
                int inputIndex = mVideoDecoder.dequeueInputBuffer(0);
                if (inputIndex >= 0) {
                    mVideoDecoder.queueInputBuffer(inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                    swLog.logi("pushEosInputBuffer");
                    break;
                }
            }
            try {
                TimeUnit.MILLISECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        swLog.logi("pushEosInputBuffer finish");
    }

    private void pushInputBuffer() {
        boolean isVideoEos = false;
        if (mVideoCommStatus == VideoCommStatus.VIDEO_COMM_STATUS_NEXT_STARTED) {
            isVideoEos = true;
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            if (mVideoExtractor.getSampleSize() <= 0) {
                isVideoEos = true;
            }
        } else {
            swLog.logd("not support");
            return;
        }

        /* SWMediaManager调用pushEosInputBuffer */
        if (isVideoEos) {
            mVideoCommStatus = VideoCommStatus.VIDEO_COMM_STATUS_CODEC_END;
            if (mOnMediaRenderListener != null) {
                swLog.logi("Video onCodecCompletion");
                mOnMediaRenderListener.onCodecCompletion(mFilePath, mCodecLastTimeUs);
            }
            return;
        }

        /* 注意：获取到inputIndex后必须执行queueInputBuffer */
        int inputIndex = mVideoDecoder.dequeueInputBuffer(0);
        if (inputIndex < 0) {
            swLog.logv("dequeueInputBuffer inputIndex " + inputIndex);
            return;
        }

        ByteBuffer inputBuffer = inputBuffers[inputIndex];
        int sampleSize = mVideoExtractor.readSampleData(inputBuffer, 0);
        int sampleIndex = mVideoExtractor.getSampleTrackIndex();
        long sampleTimeUs = mVideoExtractor.getSampleTime();

        long presentationTimeUs = sampleTimeUs + mCodecStartTimeUs;
        long sleepTimeNs = presentationTimeUs * 1000 - (System.nanoTime() - mRenderStartTimeNs);
        swLog.logv("queueInputBuffer sampleSize " + sampleSize +
                ",\tsampleTimeUs " + sampleTimeUs + ",\tpresentationTimeUs " + presentationTimeUs +
                ",\tsampleIndex " + sampleIndex +
                ", \tsleepTimeNs " + sleepTimeNs);

        if (mFirstFrameQuery) {
            mFirstFramePtsList.add(presentationTimeUs);
        }

        mVideoDecoder.queueInputBuffer(inputIndex, 0, sampleSize, presentationTimeUs, 0);
        mCodecLastTimeUs = presentationTimeUs;
        mVideoExtractor.advance();
    }

    private void getOutputBuffer() {
        int outputBufferIndex = mVideoDecoder.dequeueOutputBuffer(info, 0);
        switch (outputBufferIndex) {
            case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
                outputBuffers = mVideoDecoder.getOutputBuffers();
                swLog.logd("INFO_OUTPUT_BUFFERS_CHANGED: " + outputBuffers.length);
                break;
            case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
                swLog.logd("INFO_OUTPUT_FORMAT_CHANGED format : " + mVideoDecoder.getOutputFormat());
                break;
            case MediaCodec.INFO_TRY_AGAIN_LATER:
                swLog.logv("INFO_TRY_AGAIN_LATER");
                break;
            default:
                // All decoded frames have been rendered, we can stop playing now
                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                    swLog.logd("OutputBuffer BUFFER_FLAG_END_OF_STREAM");
//                    if (mVideoCommStatus != VideoCommStatus.VIDEO_COMM_STATUS_PAUSED) {
//                        mVideoCommStatus = VideoCommStatus.VIDEO_COMM_STATUS_RENDER_END;
//                        swLog.logd("VIDEO_COMM_STATUS_RENDER_END");
//                    }
                    mVideoDecoder.flush();
                    mVideoDecoderFlush = true;
                    if (mOnMediaRenderListener != null) {
                        mOnMediaRenderListener.onInfo(SWMediaPlayer.MEDIA_INFO_VIDEO_RENDERING_END, mLastRenderTimestampNs);
                    }
                    break;
                }

                long presentationTimeUs = info.presentationTimeUs;
                swLog.logv("dequeueOutputBuffer presentationTimeUs " + presentationTimeUs);

                long sleepTimeNs = presentationTimeUs * 1000 - (System.nanoTime() - mRenderStartTimeNs);
                long sleepTimeMs = sleepTimeNs / 1000_000;
                long renderTimestampNs = presentationTimeUs * 1000 + mRenderStartTimeNs;

                /* 视频首帧检测 */
                boolean mIsFirstFrame = false;
                if (mFirstFrameQuery) {
                    for (Long pts : mFirstFramePtsList) {
                        if (pts == presentationTimeUs) {
                            mIsFirstFrame = true;
                            break;
                        }
                    }
                }

                if (mIsFirstFrame) {
                    swLog.logd("FirstFrame presentationTimeUs: " + presentationTimeUs);
                    mFirstFrameQuery = false;/* 找到首帧，清除标记 */

                    if (mOnMediaRenderListener != null) {
                        try {
                            if (mVideoFormat.containsKey(MediaFormat.KEY_ROTATION) && (mVideoFormat.getInteger(MediaFormat.KEY_ROTATION) == 90 || mVideoFormat.getInteger(MediaFormat.KEY_ROTATION) == 270)) {
                                mOnMediaRenderListener.onVideoSizeChanged(mVideoFormat.getInteger(MediaFormat.KEY_HEIGHT), mVideoFormat.getInteger(MediaFormat.KEY_WIDTH));
                            } else {
                                mOnMediaRenderListener.onVideoSizeChanged(mVideoFormat.getInteger(MediaFormat.KEY_WIDTH), mVideoFormat.getInteger(MediaFormat.KEY_HEIGHT));
                            }
                        } catch (NullPointerException e) {
                            e.printStackTrace();
                        }
                    }

                    if (mOnMediaRenderListener != null) {
                        mOnMediaRenderListener.onInfo(SWMediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START, renderTimestampNs);
                    }

                    if (mVideoDecoderFlush) {
                        mVideoDecoderFlush = false;
                        swLog.logd("releaseOutputBuffer MEDIA_INFO_VIDEO_RENDERING_START");
                        mVideoDecoder.releaseOutputBuffer(outputBufferIndex, renderTimestampNs - 240_000_000);
                        if (mOnMediaRenderListener != null) {
                            mOnMediaRenderListener.renderTimestampNs(renderTimestampNs, presentationTimeUs);
                        }
                        break;
                    }
                }

                try {
                    if (sleepTimeMs > 500) {
                        TimeUnit.MILLISECONDS.sleep(sleepTimeMs - 500);
                        sleepTimeNs = presentationTimeUs * 1000 - (System.nanoTime() - mRenderStartTimeNs);
                        sleepTimeMs = sleepTimeNs / 1000_000;
                        renderTimestampNs = presentationTimeUs * 1000 + mRenderStartTimeNs;
                    }
                } catch (InterruptedException e) {
                    break;
                }

                if (sleepTimeMs < 0 || sleepTimeMs > 1000) {
                    mVideoDecoder.releaseOutputBuffer(outputBufferIndex, false);
                    swLog.logd("drop this video sleepTimeMs " + (sleepTimeNs / 1000_000) + "(ms)" +
                            ", presentationTimeMs " + (presentationTimeUs / 1000) + "(ms)" +
                            ", " + ((System.nanoTime() - mRenderStartTimeNs) / 1000_000) + "(ms)");
                } else {
                    mVideoDecoder.releaseOutputBuffer(outputBufferIndex, renderTimestampNs);
                    if (mOnMediaRenderListener != null) {
                        mOnMediaRenderListener.renderTimestampNs(renderTimestampNs, presentationTimeUs);
                    }
                }
                mLastRenderTimestampNs = renderTimestampNs;
                break;
        }
    }
}
